﻿//基于Jquery以及Jquery-tmpl 实现简单的一个MVVM框架
(function ($) {
    var log = console.log
    var error = console.error

    var utils = {
        isJson: function (data) {
            return typeof data == "object" && Object.prototype.toString.call(data).toLowerCase() == "[object object]" && !data.length
        },
        isFunc: function (data) {
            return typeof data == 'function'
        }
    }

    var Watcher = function (val) {
        this.parent = null
        this.context = null
        this.computed = utils.isFunc(val)
        this.isObject = utils.isJson(val)
        this.observers = []
        this.childs = {}
        this.name = null
        this.fullName = null
        this.value = val
        this.defineReactive = function (data, key, prefix) {
            this.name = key
            this.fullName = prefix ? (prefix + '.' + key) : key
            var that = this

            //重写get,set方法
            Object.defineProperty(data, key, {
                enumerable: true,
                configurable: false,
                get: function () {
                    log("get:" + key + ",value:" + that.value);
                    if (Watcher.Current) {
                        that.add(that.fullName, Watcher.Current)
                    }
                    return that.computed ? that.value.call(that.context.controller) : that.value
                },
                set: function (newVal) {
                    if (that.computed) {
                        throw that.fullName + '是复杂属性，是只读的,不能对其重新赋值'
                    }
                    log("set:" + key + ",value:" + newVal);
                    if (newVal == that.value) {
                        return;
                    }
                    that.childs = Watcher.tansform(newVal, newVal, prefix, that)
                    that.value = newVal
                    that.notify(that.fullName, newVal)
                }
            });
        }
        this.add = function (key, observer) {
            this.observers.push(observer)
        }
        this.notify = function (key, value) {
            this.observers.forEach(function (ob) {
                ob.update(value)
            })
            //递归通知元素
            if (this.childs) {
                for (var item in this.childs) {
                    var child = this.childs[item]
                    child.notify(value[item])
                }
            }
        }
    }

    Watcher.tansform = function (source, target, prefix, parent, context) {
        if (utils.isJson(source)) {
            var watchers = {}
            prefix = prefix || ''
            for (var key in source) {
                var old = parent && parent.childs && parent.childs[key]
                var watcher = old || new Watcher(source[key])
                watcher.context = context
                watcher.value = source[key]
                watcher.parent = parent
                watcher.defineReactive(target, key, prefix)

                watchers[watcher.name] = watcher

                var childPrefix = watcher.fullName
                watcher.childs = Watcher.tansform(source[key], target[key], childPrefix, watcher, context)
            }
            return watchers
        }
    }

    Watcher.Current = null;

    //监听元素变化
    var Observer = function (options) {
        this.$ele = null
        this.name = null
        this.context = null
        this.method = null
        this.readValue = null
        this.writeValue = null
        this.updateView = null
        this.options = $.extend({}, Observer.DEFAULTS, options)
        this.init = function () {
            Watcher.Current = this
            this.updateView()
            Watcher.Current = null
        }
        //更新
        this.update = function (newVal) {
            this.updateView(newVal)
        }
    }

    Observer.DEFAULTS = {
        format: null,
        name: null
    }

    //
    var EventDescriptor = function (options) {
        this.name = null
        this.method = null
        this.controller = null
        this.options = $.extend({}, EventDescriptor.DEFAULTS, options)
    }
    //可注册的事件
    EventDescriptor.EVENTS = [
        'blur',
        'change',
        'click',
        'dblclick',
        'error',
        'focus',
        'focusin',
        'focusout',
        'keydown',
        'keypress',
        'keyup',
        'mousedown',
        'mouseenter',
        'mouseleave',
        'mousemove',
        'mouseout',
        'mouseover',
        'mouseup',
        'resize',
        'scroll',
        'select',
        'submit',
        'unload',
    ]

    EventDescriptor.DEFAULTS = {
        type: 'on'
    }

    //指令集合 ： bind-{指令}
    var Binds = {};
    Binds["val"] = {
        init: function (observer) {
            if (observer.$ele.is('input,select')) {
                //监听onchange事件
                observer.$ele.on('input propertychange', function () {
                    var newVal = $(this).val()
                    observer.writeValue(newVal)
                })
            }
        },
        update: function ($ele, newVal, controller) {
            $ele.val && $ele.val(newVal)
        }
    }

    Binds["attr"] = {
        update: function ($ele, newVal, controller, observer) {
            if (!$ele.attr) return
            var name = observer.options.name
            if (name) {
                $ele.attr(name, newVal)
            } else {
                $ele.attr(newVal)
            }
        }
    }

    Binds["css"] = {
        update: function ($ele, newVal, controller, observer) {
            if (!$ele.css) return
            var name = observer.options.name
            if (name) {
                $ele.css(name, newVal)
            } else {
                $ele.css(newVal)
            }
        }
    }

    Binds["html"] = {
        update: function ($ele, newVal, controller) {
            $ele.html && $ele.html(newVal)
            //编译 newVal
            Compiler.compile($ele, controller)
        }
    }

    Binds["text"] = {
        update: function ($ele, newVal, context) {
            $ele.text && $ele.text(newVal)
        }
    }

    Binds["template"] = {
        init: function (observer) {
            EventDescriptor.EVENTS.forEach(function (e) {
                observer.$ele.on(e, '[on-' + e + ']', function (event) {
                    var $ele = $(this)
                    var method = $ele.attr('on-' + e)
                    var param = $ele.data()
                    observer.controller[method] && observer.controller[method](param, event)
                })
            })
        },
        update: function ($ele, newVal, context) {
            if (!$ele['__$template__']) {
                $ele['__$template__'] = $('<script type="text/x-jquery-tmpl">').html($ele.html())
            }
            $.tmpl && $ele.html($ele['__$template__'].tmpl(newVal))
        }
    }

    //编译器
    var Compiler = {
        _funcCache: {},
        compile: function ($ele, controller) {
            var childs = $ele.children()
            var length = $ele.children().length
            if (length > 0) {
                for (var i = 0; i < length; i++) {
                    var child = childs[i]
                    this.compileElement(child, controller)
                }
            }
            Watcher.Current = null
        },
        //编译事件
        compileEvent: function (ele, descriptor) {
            //绑定事件
            var $ele = $(ele)
            $ele.off()
            $ele[descriptor.options.type](descriptor.name, function (event) {
                var param = $ele.data()
                descriptor.controller[descriptor.method] && descriptor.controller[descriptor.method](param, event)
            })
        },
        //编译元素
        compileElement: function (ele, controller) {
            //元素中存在bind- 开始属性则绑定
            var $ele = $(ele)
            var that = this
            var attrs = ele.attributes
            var array = []
            array.forEach.call(attrs, function (attr) {
                if (that.isEvent(attr.name)) {
                    var name = attr.name.substr(3)
                    var parse = that.parseExpression(attr.value)

                    var descriptor = new EventDescriptor(parse.options)
                    descriptor.name = name
                    descriptor.method = parse.value
                    descriptor.controller = controller

                    that.compileEvent($ele, descriptor)
                }
                else if (that.isDirective(attr.name)) {
                    var method = attr.name.substr(5)
                    var parse = that.parseExpression(attr.value)

                    var observer = new Observer(parse.options)
                    observer.$ele = $ele
                    observer.name = parse.value
                    observer.controller = controller

                    //读写器
                    var writeContent = 'this.' + observer.name + '=newVal'
                    observer.writeValue = that._funcCache[writeContent] || (that._funcCache[writeContent] = (new Function('newVal', writeContent)).bind(controller))

                    var readContet = 'return this.' + observer.name
                    observer.readValue = that._funcCache[readContet] || (that._funcCache[readContet] = (new Function(readContet)).bind(controller))

                    observer.updateView = function (newVal) {
                        newVal = observer.readValue()
                        if (observer.options.format) {
                            newVal = observer.controller[observer.options.format](newVal)
                        }
                        Binds[method].update($(ele), newVal, controller, observer)
                    }

                    Binds[method].init && Binds[method].init(observer)
                    observer.init();
                }
            });
            //执行data-context标签的元素
            var childContext = [].some.call(attrs, function (i) { return i.name == "data-context" })

            if (!childContext) {
                this.compile($ele, controller)
            } else {
                $ele.controller({ parent: controller })
            }
        },
        parseExpression: function (content) {
            var contents = content.split(',')
            var source = contents[0].trim()

            var options = {}
            if (contents.length > 1) {
                contents.forEach(function (item, i) {
                    if (i == 0) return
                    var array = item.split('=')
                    options[array[0].trim()] = array[1].trim()
                })
            }
            return {
                value: source,
                options: options
            }
        },
        //是否是指令
        isDirective: function (name) {
            return name.indexOf("bind-") == 0
        },
        //是否是事件
        isEvent: function (name) {
            return name.indexOf("on-") == 0
        }
    }

    var ControllerContext = function (ele, options) {
        this.$ele = $(ele)
        this.name = this.$ele.data('context')
        this.__watches = {}
        this.options = $.extend({}, ControllerContext.DEFAULTS, options)
    }

    ControllerContext.DEFAULTS = {
        parent: null
    }

    //
    ControllerContext.prototype.init = function (options) {
        if (!this.name || typeof this.name != 'string') {
            log('不存在context或者该context已初始化')
            return
        }
        this.controller = getInstance(window[this.name])
        if (options && options.parent) {
            this.controller.parent = options.parent
        }
        this.$ele.data('context', this.controller)
        //继承基类
        $.extend(this.controller, BaseController)

        this.controller.$ele = this.$ele
        this.controller._context = this
        //绑定属性
        this.__watches = Watcher.tansform(this.controller.data, this.controller, '', null, this)

        delete this.controller.data;

        this.controller['init'] && this.controller['init']()

        Compiler.compile(this.controller.$ele, this.controller)

        this.controller['created'] && this.controller['created']()
    }


    //控制器基类 - 所有控制器默认继承次类    
    var BaseController = {
        //继承父类方法,必须在init方法中继承
        extend: function (parent) {
            if (parent.data) {
                var data = {}
                $.extend(data, parent.data)

                Watcher.tansform(data, data, '', null, this._context)
            }
            $.extend(this, parent, $.extend({}, this))
        },
        utils: utils,
        //notify: $.notify,
        //modal: $.modal,
        //http: $.http,
        //alert: $.alert

    }

    function getInstance(data) {
        return utils.isFunc(data) ? new data() : data;
    }

    function Plugin(option) {
        var $this = $(this)
        if ($this.is('[data-context]')) {
            var context = new ControllerContext(this, option)
            context.init(option)
        } else {
            $this.find('[data-context]').each(function () {
                var context = new ControllerContext(this, option)
                context.init(option)
            })
        }

    }

    $.fn.controller = Plugin

    $.extend({
        controller: {
            //扩展基类方法
            extend: function (func) {
                $.extend(BaseController, func)
            },
            //重新设置指令
            setDirective: function (key, func) {
                Binds[key] = getInstance(func)
            }
        }
    })
})($)